/*
** $Id: unihan_stdif_aux.c,v 1.00 2004/10/03 14:51:42 arren Exp $
**
** SWin X Auxilliary Objects for Chinese Linux Input Method.
* External module -- IM status panel-applet
*/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif /* HAVE_CONFIG_H */

#define _GNU_SOURCE
#include <langinfo.h>
#include <locale.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gconf/gconf-client.h>
#include <gnome.h>
#include <X11/X.h>
#include <X11/Xatom.h>
#include <X11/Xproto.h>

#include "xaux_common.h"
#include "xaux_ext_common.h"
#include "unihan_xaux.h"
#include "unihan_property.h"
#include "clipboard.h"
#include <standard_im.h>

#include "engine.h"
#include "setting.h"
#include "xaux_ext_status.h"
#include "server.h"
#include "HM.h"
#include "ime.h"
#include "imm.h"
#include "XWindow.h"


static void init_xaux_class(void);
Bool xaux_ext_Start(xaux_class_t *, aux_ext_data_t *);
Bool xaux_ext_Draw(xaux_class_t *, aux_ext_data_t *);
Bool xaux_ext_Done(xaux_class_t *, aux_ext_data_t *);

static gint client_event_handler(GtkWidget*, GdkEventClient*, gpointer);

/* the following 4 functions are for enabling and disabling the hiding of the applet */
static gboolean applet_menu_enter_notify_event_handler(
		GtkWidget *widget,
		GdkEventCrossing *event,
		gpointer user_data);
static gboolean applet_menu_leave_notify_event_handler(
		GtkWidget *widget,
		GdkEventCrossing *event,
		gpointer user_data);
static void applet_tcsc_menu_deactivate_event_handler(GtkMenuShell *menushell, gpointer user_data);

static void create_property_menu(GtkWidget *, gpointer);

/* static void destroy_im_menu(GtkWidget*, gpointer); */

static GtkWidget *create_plug(void);
static void receive_tcsc_mode(aux_ext_data_t *);
static void receive_im_list(aux_ext_data_t *);
static void receive_engine_buffer(aux_ext_data_t *);
static void switch_im_handler(GtkMenuItem *, gpointer);
static void tcsc_item_handler(GtkMenuItem *, gpointer);
static void show_about_box(void);
static gint icon_press_handler(GtkWidget *, GdkEventButton *, gpointer);
static gint pref_item_handler(GtkMenuItem *, gpointer);
static gint about_item_handler(GtkMenuItem *, gpointer);
static void plug_embedded_handler(GtkPlug *, gpointer);
static gboolean plug_client_msg_handler(GtkWidget *, GdkEventClient *, gpointer);
static void set_window_icon(void);
static gboolean is_unicode_charset(gchar *);
static void applet_window_realize(GtkWidget *, gpointer);
static void menu_position_func(GtkMenu *, gint *, gint *, gboolean *, gpointer);
static void create_im_menu(GtkWidget *w, gpointer data);

static gboolean lost_focus_timeout(gpointer data);
static void find_system_tray(void);
static void dock_plug_to_system_tray(GtkWidget *, Window);
static GdkFilterReturn manager_filter(GdkXEvent *, GdkEvent *, gpointer);

static StatusControl *status_controls_new(StatusWindow *w);
static void status_controls_free(StatusControl *c);
static void controls_current_im_updated(IMInfo *im);
static void controls_tcsc_mode_updated(UnihanTCSCMode tcsc);
static void controls_unihan_available(gboolean avail);

static StatusModel *status_model_new(void);
static void status_model_free(StatusModel *m);
static void status_im_list_set(StatusModel *m, GList *new_imList);
static void status_im_curr_list_set(StatusModel *m, GList *new_imList);
static void status_current_im_set_by_id(StatusModel *m, gchar *im_id);
static void status_tcsc_mode_set(StatusModel *m, UnihanTCSCMode tcsc);
static void status_unihan_available_set(StatusModel *m, gboolean avail);
//static char *status_get_next_im_by_id(StatusModel *m, char *id);
static void clipboard_get_handler(GtkClipboard *, GtkSelectionData *, guint, gpointer);

static StatusWindow *status_window_new(StatusModel *m);
static void status_window_free(StatusWindow *w);
static void present_applet_window(void);

static char *orig_codeset = NULL;

/* Define Aux class */
xaux_class_t xaux_class;
xaux_class_t *xc_ = NULL;
int im_ = VOID_IM_ID;
int ic_ = VOID_IC_ID;
int xbe_im = VOID_IM_ID;
int xbe_ic = VOID_IC_ID;

StatusModel *model = NULL;
StatusWindow *window = NULL;

static GtkClipboard *status_clipboard = NULL;
static GtkClipboard *user_clipboard = NULL;
static GtkWidget *plug = NULL;
static gboolean stop_sending_tcsc = FALSE;
static GtkTooltips *tooltips;
static GdkAtom commit_string_atom;
static gboolean im_menu_ignore_activate = FALSE;

static gboolean focused = FALSE;
static int focused_im = 0;
static int focused_ic = 0;

static char *locale = NULL;
static guint focus_lost_timer = 0;


#define TARGET_PLUG_ID 			1
#define TARGET_CURR_IM_LIST 		2
#define TARGET_ALL_IM_LIST      	3
#define TARGET_RAW_DATA			4
#define TARGET_CLIENT_WINDOW		6
#define TARGET_CLIPBOARD_COMMAND	30


/* ------------------------------------------------------------------------- */
int
main(int argc, char *argv[])
{
	/* 
	** Always use UTF-8 ... otherwise we need to convert everything
	** sent from the LE back from nl_langinfo(CODESET) to UTF-8!
	*/
	locale = strdup(setlocale(LC_ALL, ""));
	orig_codeset = nl_langinfo(CODESET);
	xaux_ext_use_utf8(True);
	gtk_set_locale();
	
	/* Initialize the i18n stuff */
	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
	bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR);
	textdomain(GETTEXT_PACKAGE);

	//gtk_init(&argc, &argv);
	gnome_program_init(PACKAGE, VERSION, LIBGNOMEUI_MODULE,
			   argc, argv, GNOME_PARAM_NONE);

        /* Initialize the model that represents Unihan's state */
        model = status_model_new();

	tooltips = gtk_tooltips_new();
        window = status_window_new(model);
	set_window_icon();
	
	/* 
	** this plug is for plugging into various sockets
	** sockets will be created for the GNOME 1.x panel, GNOME 2.0 panel,
	** and probably the KDE panel
	*/
	plug = create_plug();

	/*
	** create clipboards to advertise to the world about:
	** 1) the list of IMs available
	** 2) the Plug's window ID, for use for sockets
        ** 3) the list of symbols available
        ** 4) the list of words available
	*/
	status_clipboard = gtk_clipboard_get(gdk_atom_intern(STATUS_CLIPBOARD, FALSE));
	{
		GtkTargetEntry targets[6] = {
			{ PLUG_ID,		0,	TARGET_PLUG_ID },
			{ IM_CURR_LIST,		0,	TARGET_CURR_IM_LIST },
                        { IM_ALL_LIST,		0,	TARGET_ALL_IM_LIST },
			{ RAW_DATA,		0,	TARGET_RAW_DATA },
			{ CLIENT_WINDOW,	0,	TARGET_CLIENT_WINDOW },
			{ CLIPBOARD_COMMAND,	0,	TARGET_CLIPBOARD_COMMAND },
		};
		gtk_clipboard_set_with_data(status_clipboard, targets, 6,
					    clipboard_get_handler, NULL, NULL);
	}
	user_clipboard = gtk_clipboard_get(gdk_atom_intern(USER_CLIPBOARD, FALSE));

	g_signal_connect(G_OBJECT(window->appletWindow), "realize",
			 G_CALLBACK(applet_window_realize), NULL);

	gtk_widget_show_all(window->appletWindow);

	/* Register event handler */
	g_signal_connect(G_OBJECT(window->appletWindow), "client_event",
			 G_CALLBACK(client_event_handler), NULL);

	/* Register aux class */
	init_xaux_class();
	if (xaux_ext_init_classes(GDK_WINDOW_XDISPLAY(window->appletWindow->window), 
				  &xaux_class,
				  GDK_WINDOW_XWINDOW(window->appletWindow->window)) == False){
		g_error("Unihan - StatusAux: cannot register aux class!");
		return 2;
	}

        if (1) {/*(in_tray) {*/
                find_system_tray();
        }

	server_start();
	server_set_receive_handler(imm_receive_handler);
	ime_init();
	if (!HM_load_ui(UNIHAN_DEFAULT_UI_PATH))
		exit(1);
	gtk_main();

        status_window_free(window);
        status_model_free(model);

	return 0;
}


/* ========================================================================= */
/* ==================== X Auxilliary Objects related ======================= */
/* ========================================================================= */
static void 
init_xaux_class(void)
{
	char *classname = AUX_STATUS_CLASS_NAME;
	char *extexec = UNIHAN_XAUX_DIR "/stdif/xaux_ext_status";

	memset(&xaux_class, 0, sizeof(xaux_class_t));

	xaux_class.classname = classname;
	xaux_class.index = 1;
	xaux_class.extexec = extexec;
}

/* ------------------------------------------------------------------------- */
Bool
xaux_ext_Start(xaux_class_t *xc, aux_ext_data_t *aux_ext_data)
{
	int int_list[1];
	char *str_list[1];
	 
	 
        // FIXME: here we assume the first time xaux_ext_Start() gets called 
        //        is by htt_xbe
        if (xbe_im == VOID_IM_ID) {
                xbe_im = aux_ext_data->im;
                xbe_ic = aux_ext_data->ic;
        }
	
	im_ = aux_ext_data->im;
	ic_ = aux_ext_data->ic;
	xc_ = xc;

	/* tell them we're here, and send Product Key to them */
	int_list[0] = UNIHAN_AUX_STATUS_START_NOTIFY;
	send_to_engine(1, int_list, 0, NULL);

	/* FIXME: actually send Product Key here */
	/* if first run, start druid */

	/* tell them what we're made of ;) */
	int_list[0] = UNIHAN_ENGINE_SET_ENCODING;
	str_list[0] = orig_codeset;
	send_to_engine(1, int_list, 1, str_list);

	/* request list of IMs available */
	int_list[0] = UNIHAN_ENGINE_GET_IM_LIST;
	send_to_engine(1, int_list, 0, NULL);

	return True;
}

/* ------------------------------------------------------------------------- */
Bool
xaux_ext_Draw(xaux_class_t *xc, aux_ext_data_t *aux_ext_data)
{
	int type;
	
	im_ = aux_ext_data->im;
	ic_ = aux_ext_data->ic;
	xc_ = xc;

	if (aux_ext_data->integer_count < 1)
		return False;

	type = aux_ext_data->integer_list[0];
	switch (type) {
		case UNIHAN_ENGINE_GET_IM_LIST:
			receive_im_list(aux_ext_data);
			status_unihan_available_set(model, TRUE);
			break;
		case UNIHAN_ENGINE_INVALID_KEY:
			// FIXME: run druid with `invalid key'
			break;
		case UNIHAN_ENGINE_SEND_BUFFER:
			receive_engine_buffer(aux_ext_data);
			break;
		case UNIHAN_AUX_STATUS_PREV_IM:
		case UNIHAN_AUX_STATUS_NEXT_IM: {
			int int_values[1];
			char *string_values[1];
			GList *im = model->imCurrList;
			IMInfo *info = NULL;

			/* english : toggle im and switch */
			/*if (!model->currIM) {
				im = model->imCurrList;
				if (type == UNIHAN_AUX_STATUS_PREV_IM) {
					while (im->next)
						im = g_list_next(im);
				}
				info = (IMInfo *)im->data;
				int_values[0] = UNIHAN_ENGINE_SWITCH_IM;
				string_values[0] = info->id;
				send_to_engine(1, int_values, 1, string_values);
				break;
			}*/
			while (im) {
				info = (IMInfo *)im->data;
				if (g_ascii_strcasecmp(info->id, model->currIM->id) == 0)
					break;
				im = g_list_next(im);
			}
			if (type == UNIHAN_AUX_STATUS_NEXT_IM) {
				im = g_list_next(im);
				if (!im)
					im = model->imCurrList;
			}
			else {
				im = g_list_previous(im);
				if (!im) {
					im = model->imCurrList;
					while (im->next)
						im = g_list_next(im);
				}
			}
			info = (IMInfo *)im->data;
			string_values[0] = info->id;

			int_values[0] = UNIHAN_ENGINE_SWITCH_IM;

			send_to_engine(1, int_values, 1, string_values);
			break;
		}
		case UNIHAN_AUX_STATUS_SWITCH_IM: {
			gchar *cname = aux_ext_data->string_list[0].ptr;
			int i;
			UnihanPropertyArgList pp = NULL;

			if (aux_ext_data->string_count < 1)
				return False;

			focused = TRUE;
			focused_im = im_;
			focused_ic = ic_;

			status_current_im_set_by_id(model, cname);
			// FIXME:
			// check for English/HW, start timer to hide at timeout if so
			// (NOTE : I think English/HW should not be set here, le controls them.
			// So do I . Arren)
			present_applet_window();
					
#if DEBUG
			fprintf(stderr, "Current im:%s\n\r",aux_ext_data->string_list[0].ptr);
			fprintf(stderr, "String_count: %d\n\r",aux_ext_data->string_count);
#endif /* DEBUG */

			for(i = 1; i < aux_ext_data->string_count; i++)
				convert_string_to_props(aux_ext_data->integer_list[i],
						aux_ext_data->string_list[i].ptr, &pp);
			break;
		}
		case UNIHAN_AUX_STATUS_BEEP:
			gdk_beep();
			break;

		case UNIHAN_AUX_STATUS_TCSC_MODE:
			if (aux_ext_data->integer_count < 2)
				return False;

			receive_tcsc_mode(aux_ext_data);
			break;

		//case UNIHAN_AUX_GET_FOCUS:

		case UNIHAN_AUX_LOST_FOCUS:
#if DEBUG
			g_message("UNIHAN_AUX_LOST_FOCUS\n");
#endif
			//set to English, because no UNIHAN_AUX_GET_FOCUS, so do not set, otherwise
			//GET_FOCUS will not work.
			//status_current_im_set_by_id(model, "English");
			// FIXME:
			// check for English/HW, start timer to hide at timeout if so
			//present_applet_window();

			// ignore spurious lost focus events
			if ((ic_ != focused_ic) || (im_ != focused_im))
				break;
			focused = FALSE;

			// start a timer, if still not focused to a client at timeout,
			// then do something... hide, for example
			// popping up a GtkMenu grabs keyboard focus... any way to disable that?
			focus_lost_timer = g_timeout_add(200, lost_focus_timeout, NULL);
			break;
		case UNIHAN_AUX_C_UI_EVENT:
			break;
		default:
			break;
	}
	return True;
}

/* ------------------------------------------------------------------------- */
Bool
xaux_ext_Done(xaux_class_t *xc, aux_ext_data_t *aux_ext_data)
{
	gtk_main_quit();
	return True;
}

/* ------------------------------------------------------------------------- */
static gint
client_event_handler(GtkWidget *w, GdkEventClient *e, gpointer data)
{
	Bool r;
	Display *display;
	XClientMessageEvent event;

	display = GDK_WINDOW_XDISPLAY(w->window);
	event.type = ClientMessage;
	event.serial = 0;
	event.send_event = (e->send_event == TRUE) ? True : False;
	event.display = GDK_WINDOW_XDISPLAY(e->window);
	event.window = GDK_WINDOW_XWINDOW(e->window);
	event.message_type = gdk_x11_atom_to_xatom(e->message_type);
	event.format = e->data_format;
	memcpy(event.data.b, e->data.b, sizeof(event.data.b));

	r = xaux_ext_process_client_message(display, &event);

	return (r == True) ? TRUE : FALSE;
}


/* ========================================================================= */
/* ==================== Unihan state model ================================= */
/* ========================================================================= */
static StatusModel *
status_model_new(void)
{
        StatusModel *m = g_new0(StatusModel, 1);
        m->tcsc = UNIHAN_TCSC_NO_CONV;

        if (is_unicode_charset(orig_codeset)) {
                m->tcscAvailable = TRUE;
        }

        return m;
}

/* ------------------------------------------------------------------------- */
static void
status_model_free(StatusModel *m)
{
	free_im_list(m->imList);
        free_im_list(m->imCurrList);
        g_free(m);
}

/* ------------------------------------------------------------------------- */
static void
status_im_curr_list_set(StatusModel *m, GList *new_imCurrList)
{
        if (m->imCurrList) {
                free_im_list(m->imCurrList);
        }

        m->imCurrList = new_imCurrList;
}

/* ------------------------------------------------------------------------- */
static void
status_im_list_set(StatusModel *m, GList *new_imList)
{
        gchar *prevIM = NULL;

        if (m->imList) {
                // preserve the current IM selection!
                gchar *prevIM = "English";
                if (m->currIM) {
                        prevIM = m->currIM->cname;
                }
 
		free_im_list(m->imList);
        }

        m->imList = new_imList;
        m->currIM = NULL;

        if (prevIM)
                status_current_im_set_by_id(m, prevIM);
	if (prevIM == NULL)
	{
#if DEBUG
		printf("current im english and hide the property button!!!\n\r");
#endif
	}
}

/* ------------------------------------------------------------------------- */
static void
status_current_im_set_by_id(StatusModel *m, gchar *im_id)
{
        IMInfo *newIM = NULL;
        
        if (!strcmp("English", im_id)) {
                newIM = NULL;
        } else {
                GList *imList_ = m->imList;
                while (imList_) {
                        IMInfo *im = (IMInfo *)imList_->data;
                        if (!strcmp(im->cname, im_id)) {
                                newIM = im;
                                break;
                        }
                        imList_ = imList_->next;
                }
        }

        if (newIM != m->currIM) {
                m->currIM = newIM;
                
                controls_current_im_updated(newIM);
        }
}

/*static void
status_current_im_load_properties(int num_properties, UnihanPropertyArgList plist)
{
	UnihanPropertyArgList pp = plist;

	//model->procount = num_properties;


	if(model->propertyList) {
		destroy_im_property((UnihanPropertyArgList)model->propertyList);
	}

	if (num_properties > 2) {
		gtk_widget_show(window->controls->propertyButton);
#if DEBUG
		printf("show propertybutton okok!!!/n/r");
#endif
	}	
						
	if(pp)
		model->propertyList = (void*)pp;	
	else
		model->propertyList = NULL;

	while(pp) {
		if(pp->type == UNIHAN_PROPERTY_RUNTIME) {
			if(pp->UnihanProperty.rt_property.rt_type ==
				UNIHAN_RUNTIME_PROPERTY_UNSPEC) {
				pp->UnihanProperty.rt_property.value = 0;
				break;
			}
			int protype = pp->UnihanProperty.rt_property.rt_type;
			int runtime_property = pp->UnihanProperty.rt_property.value;
			switch(protype) {
			case UNIHAN_RUNTIME_PROPERTY_TCSC:
				//gtk_widget_show(window->controls->tcscButton);
				status_tcsc_mode_set(model, runtime_property);
				break; 

			case UNIHAN_RUNTIME_PROPERTY_STATE_EN_CH:
				break;	

			default:
				break;                          
			} // switch

		}
		pp = pp->next;
	}
}*/
/*---------------------------------------------------------------------------*/

/*static char *
status_get_next_im_by_id(StatusModel *m, char *id)
{
	char *id_next;
	GList *im_list = m->imList;
	GList *im_curr_list = m->imCurrList;
	IMInfo *info_curr = NULL;
	IMInfo *info = NULL;
	gboolean found = FALSE;

	while(im_list) {
		info = (IMInfo *)im_list->data;
		if (g_ascii_strcasecmp(info->cname, id) == 0)
			break;
		im_list = g_list_next(im_list);
	}

	im_list = g_list_next(im_list);
	if (!im_list)
		im_list = m->imList;

	while (im_list) {
		info = (IMInfo *)im_list->data;
		im_curr_list = m->imCurrList;
		while (im_curr_list) {
			info_curr = (IMInfo *)im_curr_list->data;
			if (g_ascii_strcasecmp(info->cname, info_curr->cname) == 0) {
				found = TRUE;
				break;
			}
			im_curr_list = g_list_next(im_curr_list);
			//if (!im_curr_list)
			//	im_curr_list = m->imCurrList;
		}
		if (found)
			break;
		im_list = g_list_next(im_list);
		if (!im_list)
			im_list = m->imList;
	}
	id_next = info_curr->id;
	return id_next;
}*/

/* ------------------------------------------------------------------------- */
static void
status_tcsc_mode_set(StatusModel *m, UnihanTCSCMode tcsc)
{
        if (m->tcscAvailable == FALSE)
                tcsc = UNIHAN_TCSC_NO_CONV;

        if (m->tcsc != tcsc) {
                m->tcsc = tcsc;
        }
        controls_tcsc_mode_updated(tcsc);
}

/* ------------------------------------------------------------------------- */
static void
status_unihan_available_set(StatusModel *m, gboolean avail)
{
#if TIMEBOMB
        if (expired)    // Unihan never starts!
                avail = FALSE;
#endif

        if (m->unihanAvailable != avail) {
                m->unihanAvailable = avail;
        }

        controls_unihan_available(avail);
}

/* ========================================================================= */
/* ==================== Widgets creation =================================== */
/* ========================================================================= */
static GtkWidget *
create_unihan_icon(void)
{
	GtkWidget *w = gtk_event_box_new();
	GtkWidget *pix = gnome_pixmap_new_from_file_at_size(UNIHAN_DATA_DIR "/images/icon.png", 16, 16);
	g_signal_connect(G_OBJECT(w), "button_press_event", G_CALLBACK(icon_press_handler), NULL);
	gtk_container_add(GTK_CONTAINER(w), pix);
        return w;
}

/* ------------------------------------------------------------------------- */
static GtkWidget *
create_im_label(void)
{
        return gtk_label_new(_("English"));
}

/* ------------------------------------------------------------------------- */
static GtkWidget *
create_im_button(GtkLabel *im_label)
{
	GtkWidget *w = gtk_button_new();
	gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
	g_object_set(G_OBJECT(w), "can-focus", FALSE, NULL);
	gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(im_label));
	g_signal_connect(G_OBJECT(w), "clicked",
			 G_CALLBACK(create_im_menu), NULL);
        return w;
}

static GtkWidget *
create_property_label(void)
{
	return gtk_label_new(_("property"));
}

/*---------------------------------------------------------------------------*/
static GtkWidget *
create_property_button(GtkLabel *property_label)
{
	GtkWidget *w = gtk_button_new();
	gtk_button_set_relief(GTK_BUTTON(w), GTK_RELIEF_NONE);
	g_object_set(G_OBJECT(w), "can-focus", FALSE, NULL);
	gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(property_label));
	gtk_tooltips_set_tip(tooltips, w, (_("hello world")), NULL);
	g_signal_connect(G_OBJECT(w), "clicked", 
	         G_CALLBACK(create_property_menu), NULL);
	return w;
}

/* ------------------------------------------------------------------------- */
static void
create_property_menu(GtkWidget *w, gpointer data)
{
	GtkWidget *menu = gtk_menu_new();
	GtkWidget *submenu = gtk_menu_new();
	GSList *group = NULL;
	GtkWidget *tcsc;
	GtkWidget *noconv;
	GtkWidget *tctosc;
	GtkWidget *sctotc;
	UnihanPropertyArgList property_list = NULL;

	//g_signal_connect(G_OBJECT(submenu), "deactivate",
			//G_CALLBACK(applet_tcsc_menu_deactivate_event_handler), NULL);
	g_signal_connect(G_OBJECT(menu), "enter-notify-event",
			G_CALLBACK(applet_menu_enter_notify_event_handler), NULL);
	g_signal_connect(G_OBJECT(menu), "leave-notify-event",
			G_CALLBACK(applet_menu_leave_notify_event_handler), NULL);
	
	if(model->propertyList)
		property_list = (UnihanPropertyArgList)model->propertyList;
	while(property_list) {
		if(property_list->type == UNIHAN_PROPERTY_RUNTIME) {
			switch(property_list->UnihanProperty.rt_property.rt_type) {
			case UNIHAN_RUNTIME_PROPERTY_TCSC:
				tcsc = gtk_menu_item_new_with_label(_("tcsc"));
				//groupp = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(tcsc));
					gtk_widget_show(tcsc);
				//g_signal_connect(G_OBJECT(tcsc), "deactivate",
					//G_CALLBACK(applet_tcsc_menu_deactivate_event_handler),
					//NULL);
				gtk_menu_shell_append(GTK_MENU_SHELL(menu), tcsc);
				gtk_menu_item_set_submenu(GTK_MENU_ITEM(tcsc), submenu);
				noconv = gtk_radio_menu_item_new_with_mnemonic(group, _("_No conversion"));
				//gtk_radio_menu_item_set_group(GTK_RADIO_MENU_ITEM(noconv), group);
				group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(noconv));
				gtk_menu_append(GTK_MENU(submenu), noconv);
				gtk_widget_show(noconv);
				g_signal_connect((gpointer)noconv, "activate",
					G_CALLBACK(tcsc_item_handler), GINT_TO_POINTER(0));
				tctosc = gtk_radio_menu_item_new_with_mnemonic(group,
					_("_Traditional -> simplified"));
				//gtk_radio_menu_item_set_group(GTK_RADIO_MENU_ITEM(tctosc), group);
				group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(tctosc));
				gtk_menu_append(GTK_MENU(submenu), tctosc);
				gtk_widget_show(tctosc);
				g_signal_connect((gpointer)tctosc, "activate",
					G_CALLBACK(tcsc_item_handler), GINT_TO_POINTER(1));

				sctotc = gtk_radio_menu_item_new_with_mnemonic(group,
						_("_Simplified -> traditional"));
				group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(sctotc));
				//gtk_radio_menu_item_set_group(GTK_RADIO_MENU_ITEM(sctotc), group);
				gtk_menu_append(GTK_MENU(submenu), sctotc);
				//gtk_widget_grab_focus(sctotc);
				gtk_widget_show(sctotc);
				g_signal_connect((gpointer)sctotc, "activate",
					G_CALLBACK(tcsc_item_handler),GINT_TO_POINTER(2));
				break;
			default:
				break;	
			}
		}
		property_list = property_list->next;
	}

	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, 
			menu_position_func, w, 1, GDK_CURRENT_TIME);
}

/* ------------------------------------------------------------------------- */
static GtkWidget *
create_tcsc_menu(void)
{
	GtkWidget *menu = gtk_menu_new();
	g_signal_connect(G_OBJECT(menu), "deactivate",
			 G_CALLBACK(applet_tcsc_menu_deactivate_event_handler), NULL);

        GtkWidget *mitem;
        GSList *group = NULL;
        mitem = gtk_radio_menu_item_new_with_mnemonic(group, _("_No conversion"));
        group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(mitem));
        g_signal_connect(G_OBJECT(mitem), "activate",
                         G_CALLBACK(tcsc_item_handler), GINT_TO_POINTER(0));
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
        mitem = gtk_radio_menu_item_new_with_mnemonic(group, _("_Traditional -> simplified"));
        group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(mitem));
        g_signal_connect(G_OBJECT(mitem), "activate",
                         G_CALLBACK(tcsc_item_handler), GINT_TO_POINTER(1));
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
        mitem = gtk_radio_menu_item_new_with_mnemonic(group, _("_Simplified -> traditional"));
        g_signal_connect(G_OBJECT(mitem), "activate",
                         G_CALLBACK(tcsc_item_handler), GINT_TO_POINTER(2));
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);

        return menu;
}

/* ------------------------------------------------------------------------- */
static GtkWidget *
create_main_menu(void)
{
        GtkWidget *menu = gtk_menu_new();
	GtkWidget *mitem = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, NULL);
	g_signal_connect(G_OBJECT(mitem), "activate",
			 G_CALLBACK(pref_item_handler), NULL);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
	mitem = gtk_separator_menu_item_new();
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
	mitem = gtk_image_menu_item_new_from_stock(GNOME_STOCK_ABOUT, NULL);
	g_signal_connect(G_OBJECT(mitem), "activate",
			 G_CALLBACK(about_item_handler), NULL);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
	gtk_widget_show_all(menu);

        return menu;
}

/* ------------------------------------------------------------------------- */
static StatusControl *
status_controls_new(StatusWindow *w)
{
        StatusControl *c = g_new0(StatusControl, 1);
        w->controls = c;
        
	c->hbox = gtk_hbox_new(FALSE, 0);

	// Unihan Logo
        c->icon = create_unihan_icon();
	g_object_set(G_OBJECT(c->icon), "can-focus", FALSE, NULL);
	gtk_box_pack_start(GTK_BOX(c->hbox), c->icon, FALSE, FALSE, 0);
	
	// IM selection menu
        c->imLabel = create_im_label();
	g_object_set(G_OBJECT(c->imLabel), "can-focus", FALSE, NULL);
        c->imButton = create_im_button(GTK_LABEL(c->imLabel));
	gtk_box_pack_start(GTK_BOX(c->hbox), c->imButton, FALSE, FALSE, 0);

	// tcsc menu
	/* if (w->model->tcscAvailable) {
                c->tcscLabel = create_tcsc_label();
		g_object_set(G_OBJECT(c->tcscLabel), "can-focus", FALSE, NULL);
		c->tcscButton = create_tcsc_button(GTK_LABEL(c->tcscLabel));
		gtk_box_pack_start(GTK_BOX(c->hbox), c->tcscButton, FALSE, FALSE, 0);
		g_object_set(G_OBJECT(c->tcscButton), "can-focus", FALSE, NULL);
		gtk_box_pack_start(GTK_BOX(c->hbox), c->tcscButton, FALSE, FALSE, 0);
	} */

	c->propertyLabel = create_property_label();
        g_object_set(G_OBJECT(c->propertyLabel), "can-focus", FALSE, NULL);
        c->propertyButton = create_property_button(GTK_LABEL(c->propertyLabel));
        gtk_box_pack_start(GTK_BOX(c->hbox), c->propertyButton, FALSE, FALSE, 0);

        gboolean avail = w->model->unihanAvailable;
	gtk_widget_set_sensitive(c->imButton, avail);
        if (model->tcscAvailable)
                gtk_widget_set_sensitive(c->tcscButton, avail);

	gtk_widget_show_all(c->hbox);
        gtk_container_add(GTK_CONTAINER(w->appletFrame), c->hbox);

	return c;
}

/* ------------------------------------------------------------------------- */
static void
status_controls_free(StatusControl *c)
{
        g_free(c);
        // FIXME: more to free here
}


/* ------------------------------------------------------------------------- */
static void
controls_current_im_updated(IMInfo *im)
{
        gchar *name = _("English");

        if (im)
                name = _(im->cname);

        gtk_label_set_text(GTK_LABEL(window->controls->imLabel), name);
}

/* ------------------------------------------------------------------------- */
static void
controls_tcsc_mode_updated(UnihanTCSCMode tcsc)
{
	gchar *str;
	switch (tcsc) {
		case UNIHAN_TCSC_TO_SIMP: // 1
			str = _("->SC");
			break;
		case UNIHAN_TCSC_TO_TRAD: // 2
			str = _("->TC");
			break;
		default:
                        tcsc = UNIHAN_TCSC_NO_CONV;
			str = _("NoConv");
	}
	gtk_label_set_text(GTK_LABEL(window->controls->tcscLabel), str);

        stop_sending_tcsc = TRUE;
#if NEW_PROPERTY
	//GList *menu = gtk_container_get_children(GTK_CONTAINER(window->tcscMenu));
        //GtkWidget *mi = (GtkWidget *)g_list_nth_data(menu, tcsc);
        //if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(mi)))
        //       gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(mi), TRUE);
#else
        GList *menu = gtk_container_get_children(GTK_CONTAINER(window->tcscMenu));
        GtkWidget *mi = (GtkWidget *)g_list_nth_data(menu, tcsc);
        if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(mi)))
                gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(mi), TRUE);
#endif
}

/* ------------------------------------------------------------------------- */
static void
controls_unihan_available(gboolean avail)
{
	gtk_widget_set_sensitive(window->controls->imButton, avail);
        /*if (model->tcscAvailable)
                gtk_widget_set_sensitive(window->controls->tcscButton, avail);*/
}

/* ------------------------------------------------------------------------- */
static void
receive_im_list(aux_ext_data_t *aux_ext_data)
{
	int im_count, i;

	im_count = aux_ext_data->string_count / 2;

#if DEBUG
	fprintf(stderr, "XAUX : received IM list, im_count = %d\n", im_count);
#endif

        GList *im_list = NULL;
        GList *im_curr_list = NULL;
	for (i = 0; i < im_count; i++) {
		IMInfo *iminfo;
		iminfo = g_new0(IMInfo, 1);
		iminfo->id = g_strdup(aux_ext_data->string_list[i*2].ptr);
		iminfo->cname = g_strdup(_(aux_ext_data->string_list[i*2+1].ptr));
		im_list = g_list_append(im_list, iminfo);
	}

        /* create im_curr_list */
        im_curr_list = get_curr_list_by_im_all_list(im_list);

        status_im_list_set(model, im_list);
        status_im_curr_list_set(model, im_curr_list);

	return;
}

static void
receive_engine_buffer(aux_ext_data_t *aux_ext_data)
{
#if DEBUG
	fprintf(stderr, "XAUX : receive engine buffer\n");
#endif
	char *buffer;
	int buf_len;
	int len = 0;
	int i;
	int x, y;

	buf_len = aux_ext_data->integer_list[1];

	buffer = (char *)malloc(sizeof(char)*buf_len);
	bzero(buffer, sizeof(char)*buf_len);

	for (i=0; i<aux_ext_data->string_count; i++) {
		if (strcmp(aux_ext_data->string_list[i].ptr, STR_BRK_POINTER) == 0) {
			buffer[len] = 0x0;
			len++;
		} else {
			strncpy(buffer+len, aux_ext_data->string_list[i].ptr,
					aux_ext_data->string_list[i].length);
			len += aux_ext_data->string_list[i].length;
			*(buffer+len) = 0x0;
			len++;
		}
	}

	get_caret_point(aux_ext_data->focuswin, aux_ext_data->point, &x, &y);
	//(*ime_helper_functions.update_spot_location)(0, NULL, x, y);
	(*ime_helper_functions.process_ime_event)(0, NULL, (void *)buffer, buf_len);
	//receive_buffer_handler((void *)buffer, (size_t)buf_len);
	free(buffer);
}

/* ------------------------------------------------------------------------- */
static void
receive_tcsc_mode(aux_ext_data_t *aux_ext_data)
{
	int tcsc = aux_ext_data->integer_list[1];
        status_tcsc_mode_set(model, tcsc);
}

#if TIMEBOMB
/* ------------------------------------------------------------------------- */
static void
demo_expired(void)
{
        expired = TRUE;

        // disable everything
        status_unihan_available_set(model, FALSE);

        // tell the user about this
        GtkWidget *dialog = gtk_dialog_new_with_buttons(_("Unihan 2 Expired"),
                        NULL,
                        GTK_DIALOG_NO_SEPARATOR,
                        GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
                        NULL);
        gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
        gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
        gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
        gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), FALSE);

        GtkWidget *hbox = gtk_hbox_new(FALSE, 12);
        gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE, TRUE, 0);
        gtk_container_set_border_width(GTK_CONTAINER(hbox), 6);

        GtkWidget *icon = gtk_image_new_from_stock("gtk-dialog-error", GTK_ICON_SIZE_DIALOG);
        gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 0);
        gtk_misc_set_alignment(GTK_MISC(icon), 0, 0);

        GtkWidget *vbox = gtk_vbox_new(FALSE, 6);
        gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);

        GtkWidget *label = gtk_label_new(_("<b>This demo version of Unihan 2 has expired.</b>\n\nChinese input is now disabled.\n\nPlease contact Sun Wah Linux for more information on Unihan 2."));
        gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
        GTK_WIDGET_SET_FLAGS(label, GTK_CAN_FOCUS);
        gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
        gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
        gtk_label_set_selectable(GTK_LABEL(label), TRUE);
        gtk_misc_set_alignment(GTK_MISC(label), 0, 0);

        GtkWidget *button1 = gnome_href_new("http://www.swhss.com.cn", "http://www.swhss.com.cn");
        gtk_box_pack_start(GTK_BOX(vbox), button1, FALSE, FALSE, 0);

        gtk_button_box_set_layout(GTK_BUTTON_BOX(GTK_DIALOG(dialog)->action_area), GTK_BUTTONBOX_END);

#if 0
        GtkWidget *button1 = gtk_button_new();
        gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button1, GTK_RESPONSE_OK);
        GtkWidget *alignment1 = gtk_alignment_new(0.5, 0.5, 0, 0);
        gtk_container_add(GTK_CONTAINER(button1), alignment1);
        GtkWidget *hbox2 = gtk_hbox_new(FALSE, 2);
        gtk_container_add(GTK_CONTAINER(alignment1), hbox2);
        GtkWidget *image2 = gtk_image_new_from_stock("gtk-jump-to", GTK_ICON_SIZE_BUTTON);
        gtk_box_pack_start(GTK_BOX(hbox2), image2, FALSE, FALSE, 0);
        GtkWidget *label2 = gtk_label_new_with_mnemonic(_("Unihan _website"));
        gtk_box_pack_start(GTK_BOX(hbox2), label2, FALSE, FALSE, 0);
#endif

        g_signal_connect(G_OBJECT(dialog), "response", 
                         G_CALLBACK(gtk_widget_destroy), NULL);
        gtk_widget_show_all(dialog);
}
#endif

/* ------------------------------------------------------------------------- */
static void
present_applet_window(void)
{
        if (focus_lost_timer != 0) {
                g_source_remove(focus_lost_timer);
                focus_lost_timer = 0;
        }
	gtk_widget_set_sensitive(window->controls->imButton, window->model->unihanAvailable);
	if (!window->runAsApplet) {
                gtk_widget_show(window->appletWindow);
                gdk_window_raise(window->appletWindow->window);
        }
}

/* ------------------------------------------------------------------------- */
static gboolean
lost_focus_timeout(gpointer data)
{
	if (!window->runAsApplet && !focused && !window->doNotHide) {
		gtk_widget_hide(window->appletWindow);
        }
	gtk_widget_set_sensitive(window->controls->imButton, FALSE);
        focus_lost_timer = 0;
	return FALSE;
}

/* ------------------------------------------------------------------------- */
static gboolean
applet_menu_enter_notify_event_handler(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
{
	window->doNotHide = TRUE;
	return FALSE;
}

/* ------------------------------------------------------------------------- */
static gboolean
applet_menu_leave_notify_event_handler(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
{
	window->doNotHide = FALSE;
	return FALSE;
}

/* ------------------------------------------------------------------------- */
static void
applet_tcsc_menu_deactivate_event_handler(GtkMenuShell *menushell, gpointer user_data)
{
	window->doNotHide = FALSE;
}


/* ------------------------------------------------------------------------- */
static void
show_about_box(void)
{
	GtkWidget *about;
	GtkWidget *hbox, *vbox, *label;
	
	gtk_rc_parse_string("style 'unihan-about-box-style'\n"
			    "{\n"
			    "bg[NORMAL] = 'white'\n"
			    "bg_pixmap[NORMAL] = '<none>'\n"
			    "}\n"
			    "widget 'unihan-about-box' style 'unihan-about-box-style'");

	about = gtk_dialog_new_with_buttons(_("About Unihan"),
					    NULL,
					    GTK_DIALOG_NO_SEPARATOR,
					    GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
					    NULL);
	gtk_widget_set_name(about, "unihan-about-box");
        gtk_container_set_border_width(GTK_CONTAINER(about), 6);
	
	//zzc-by-add
#if NEW_PROPERTY
	hbox = gtk_hbox_new(FALSE, 10);
#else
	hbox = gtk_hbox_new(FALSE, 7 );
#endif
	//vbox=gtk_vbox_new(NULL,NULL);
	gtk_container_set_border_width(GTK_CONTAINER(hbox), 7);
#if NEW_PROPERTY
	vbox=gtk_vbox_new(FALSE, 0);
#else
	vbox=gtk_vbox_new(FALSE, 0);
#endif
	gtk_container_set_border_width(GTK_CONTAINER(vbox),7);	
	gtk_box_pack_start(GTK_BOX(hbox), vbox,TRUE,TRUE,1);
	//gtk_box_pack_start_defaults(GTK_BOX(hbox), vbox);
	label = gtk_label_new(NULL);
	gtk_label_set_markup(GTK_LABEL(label), _("Unihan Chinese input system"));
	//gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 2);
	label = gtk_label_new(NULL);
	gtk_label_set_markup(GTK_LABEL(label), g_strdup_printf(_("version %s"), VERSION));
	//gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
	label = gtk_image_new_from_file(UNIHAN_DATA_DIR "/images/logo.jpg");
	gtk_box_pack_start_defaults(GTK_BOX(vbox), label);
	//zzc-end
	//hbox = gtk_hbox_new(FALSE, 12);
	/*gtk_container_set_border_width(GTK_CONTAINER(hbox), 6);
	label = gtk_image_new_from_file(UNIHAN_DATA_DIR "/images/logo.jpg");
	gtk_box_pack_start_defaults(GTK_BOX(hbox), label);

	vbox = gtk_vbox_new(FALSE, 6);
	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
	label = gtk_label_new(NULL);
	gtk_label_set_markup(GTK_LABEL(label), _("Unihan Chinese input system"));
	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 4);
	label = gtk_label_new(NULL);
	gtk_label_set_markup(GTK_LABEL(label), g_strdup_printf(_("version %s"), VERSION));
	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
       */
#if TIMEBOMB
        if (expire_date != 0) {
                char date[2048];
                struct tm datetime;
                gmtime_r(&expire_date, &datetime);
                memset(date, 0, 2048);
                strftime(date, 2047, _("This demo expires on %A, %d %B %Y."), &datetime);
                label = gtk_label_new(NULL);
                gtk_label_set_markup(GTK_LABEL(label), date);
                gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
                gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
              	//gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
		 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
        }
#endif
	label = gtk_label_new(NULL);
	gtk_label_set_markup(GTK_LABEL(label), _("Copyright (C) 2002-2004 Sun Wah Linux Ltd."));
	//gtk_misc_set_alignment(GTK_MISC(label), 0.0, 1.0);
#if NEW_PROPERTY
	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
#else		
        //gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
#endif
	//gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
	label = gtk_label_new(NULL);
	gtk_label_set_markup(GTK_LABEL(label), _("Portions Copyright (C) Sun Microsystems, Inc.\nZhineng ABC 6.0 Input Method invented and designed by Zhu Shoutao, Beijing University.\nUnispim 3.0 Input Method invented and designed by Unispim co.ltd."));
//zzc
	//	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 1.0);
        gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
	//zzc
	//gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
	label = gnome_href_new("http://unihan.sw-linux.com/", 
			       "http://unihan.sw-linux.com/");
	//zzc
	//gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
	//gtk_box_pack_start_defaults(GTK_BOX(vbox), hbox);
	//hbox = gtk_hbox_new(FALSE, 12);
	//gtk_container_set_border_width(GTK_CONTAINER(hbox), 12);
	//gtk_box_pack_start_defaults(GTK_BOX(hbox), vbox);
	gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(about)->vbox),hbox);
	g_signal_connect(G_OBJECT(about), "response",
			 G_CALLBACK(gtk_widget_destroy), NULL);
	gtk_dialog_set_default_response(GTK_DIALOG(about), GTK_RESPONSE_CLOSE);
	gtk_widget_show_all(GTK_WIDGET(about));
}

/* ------------------------------------------------------------------------- */
static gint
icon_press_handler(GtkWidget *w, GdkEventButton *e, gpointer data)
{
	if (e->type != GDK_BUTTON_PRESS)
		return FALSE;
	
	if (e->button == 3
	    || (e->button == 1 && window->runAsApplet == TRUE)) {
		gtk_menu_popup(GTK_MENU(window->mainMenu), NULL, NULL, 
			       menu_position_func, w,
			       e->button, e->time);
		return TRUE;
	}

	if (e->button == 1 && window->runAsApplet == FALSE) {
		gtk_window_begin_move_drag(GTK_WINDOW(window->appletWindow), 
                                           e->button, 
				           e->x_root, e->y_root, e->time);
		return TRUE;
	}

	return FALSE;
}

/* ------------------------------------------------------------------------- */
static gint 
pref_item_handler(GtkMenuItem *mi, gpointer data)
{
	int pid;
	char *const argv[1] = { BINDIR "/unihan2-prefs" };

	pid = gnome_execute_async(NULL, 1, argv);
#if DEBUG
	g_message("pid = %d", pid);
#endif
	if (pid == -1) {
		GtkWidget *dialog = gtk_message_dialog_new(NULL, 0, 
				GTK_MESSAGE_ERROR,
				GTK_BUTTONS_CLOSE,
				_("Could not start the Unihan Preferences utility."));
		g_signal_connect(G_OBJECT(dialog), "response",
				G_CALLBACK(gtk_widget_destroy), NULL);
		gtk_widget_show_all(dialog);
	}
	return TRUE;
}

/* ------------------------------------------------------------------------- */
static gint 
about_item_handler(GtkMenuItem *mi, gpointer data)
{
	show_about_box();
	return TRUE;
}

#define UNIHAN_STATUS_HIDE_ICON		1
#define UNIHAN_STATUS_ABOUT_ITEM	2
#define UNIHAN_STATUS_PREFERENCES_ITEM	3

/* ------------------------------------------------------------------------- */
static gboolean 
plug_client_msg_handler(GtkWidget *w, GdkEventClient *e, gpointer data)
{
	char *atom = gdk_atom_name(e->message_type);
	gboolean isUIControl = !strcmp("_UNIHAN_STATUS_UI_CONTROL", atom);

	g_free(atom);
	if (!isUIControl)
		return FALSE;

	switch (e->data.l[0]) {
		case UNIHAN_STATUS_HIDE_ICON:
			gtk_widget_hide(window->controls->icon);
			break;
		case UNIHAN_STATUS_PREFERENCES_ITEM:
			pref_item_handler(NULL, NULL);
			break;
		case UNIHAN_STATUS_ABOUT_ITEM:
			about_item_handler(NULL, NULL);
			break;
	}

	return TRUE;
}

/* ------------------------------------------------------------------------- */
static void
recreate_controls(void)
{
	// plug was destroyed!
	// need to recreate plugs and controls
	// but assume we won't run as applet at first
	window->runAsApplet = FALSE;
        window->doNotHide = FALSE;

        g_free(window->controls);
        window->controls = status_controls_new(window);
	plug = create_plug();
	gtk_widget_show_all(window->appletWindow);
}

/* ------------------------------------------------------------------------- */
static gboolean
plug_destroyed_oops_handler(GtkWidget *w, GdkEvent *e, gpointer data)
{
	// The GdkWindow got destroyed, but the GtkWidget is not.
	// We need to explicitly destroy the GtkWidget too.
	// Perhaps the socket crashed?
	gtk_widget_destroy(w);
	return TRUE;
}

/* ------------------------------------------------------------------------- */
static gboolean
plug_destroy_handler(GtkObject *o, gpointer data)
{
	// If the socket is removed from the panel through "normal"
	// means, the plug will be destroyed properly, and reach here.
	recreate_controls();
	return TRUE;
}

/* ------------------------------------------------------------------------- */
static GtkWidget *
create_plug(void)
{
	GtkWidget *newplug = gtk_plug_new(0);
	g_signal_connect(G_OBJECT(newplug), "embedded",
			 G_CALLBACK(plug_embedded_handler), NULL);
	g_signal_connect(G_OBJECT(newplug), "destroy-event",
			 G_CALLBACK(plug_destroyed_oops_handler), NULL);
	g_signal_connect(G_OBJECT(newplug), "destroy",
			 G_CALLBACK(plug_destroy_handler), NULL);
	g_signal_connect(G_OBJECT(newplug), "client-event",
			 G_CALLBACK(plug_client_msg_handler), NULL);
	return newplug;
}

/* ------------------------------------------------------------------------- */
static void
find_system_tray(void)
{
	char *systray_atom_name = NULL;
	Atom systray_atom = None;
	Window systray = None;
	Display *dpy = GDK_DISPLAY();
	Screen *xscreen = DefaultScreenOfDisplay(dpy);
	Window rootwin = None;
	XSetWindowAttributes xswa;

	// atom for the current screen
	systray_atom_name = g_strdup_printf("_NET_SYSTEM_TRAY_S%d", XScreenNumberOfScreen(xscreen));
	systray_atom = XInternAtom(dpy, systray_atom_name, True);
	g_free(systray_atom_name);
	if (systray_atom != None) {
		// attempt to get the system tray window
		gdk_error_trap_push();
		systray = XGetSelectionOwner(dpy, systray_atom);
		XSync(dpy, False);
		if ((gdk_error_trap_pop() == 0) && (systray != None))
			dock_plug_to_system_tray(plug, systray);
	}

	// if systray starts afterwards, we want to know that too
	memset(&xswa, 0, sizeof(xswa));
	xswa.event_mask = StructureNotifyMask;
	rootwin = RootWindow(dpy, XScreenNumberOfScreen(xscreen));
	XChangeWindowAttributes(dpy, rootwin, CWEventMask, &xswa);
	gdk_add_client_message_filter(gdk_atom_intern("MANAGER", FALSE), manager_filter, NULL);
}

/* ------------------------------------------------------------------------- */
static GdkFilterReturn
manager_filter(GdkXEvent *xev, GdkEvent *gdkev, gpointer data)
{
	XEvent *ev = (XEvent *)xev;
	Display *dpy = GDK_DISPLAY();
	Screen *xscreen = DefaultScreenOfDisplay(dpy);
	Window rootwin = RootWindow(dpy, XScreenNumberOfScreen(xscreen));
	char *systray_atom_name = NULL;
	char *ev_atom_name = NULL;
	int strcmp_r;

	// already embedded into something?
	if (window->runAsApplet)
		return GDK_FILTER_CONTINUE;

	if (ev->xany.window != rootwin)
		return GDK_FILTER_CONTINUE;
	
	systray_atom_name = g_strdup_printf("_NET_SYSTEM_TRAY_S%d", XScreenNumberOfScreen(xscreen));
	ev_atom_name = XGetAtomName(dpy, (Atom)ev->xclient.data.l[1]);
	strcmp_r = strcmp(systray_atom_name, ev_atom_name);
	g_free(systray_atom_name);
	XFree(ev_atom_name);
	// is this the systray announcement?
	if (strcmp_r != 0)
		return GDK_FILTER_CONTINUE;

	dock_plug_to_system_tray(plug, (Window)ev->xclient.data.l[2]);
	return GDK_FILTER_REMOVE;
}

/* ------------------------------------------------------------------------- */
static void
dock_plug_to_system_tray(GtkWidget *p, Window systray)
{
	XEvent ev;
	Display *dpy = GDK_DISPLAY();

	memset(&ev, 0, sizeof(ev));
	ev.xclient.type = ClientMessage;
	ev.xclient.window = systray;
	ev.xclient.message_type = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", False);
	ev.xclient.format = 32;
	ev.xclient.data.l[0] = CurrentTime;
	ev.xclient.data.l[1] = 0; // SYSTEM_TRAY_REQUEST_DOCK == 0
	ev.xclient.data.l[2] = gtk_plug_get_id(GTK_PLUG(p));

	gdk_error_trap_push();
	XSendEvent(dpy, systray, False, NoEventMask, &ev);
	XSync(dpy, False);
	gdk_error_trap_pop();	// silently ignore errors
}


/* ------------------------------------------------------------------------- */

/* ------------------------------------------------------------------------- */
static StatusWindow *
status_window_new(StatusModel *m)
{
        StatusWindow *w = g_new0(StatusWindow, 1);
        w->model = m;

        w->runAsApplet = FALSE;
        w->appletWindow = gtk_window_new(GTK_WINDOW_POPUP);
	gtk_window_set_wmclass(GTK_WINDOW(w->appletWindow), "unihan", "StatusAux");
	gtk_window_set_title(GTK_WINDOW(w->appletWindow), "Unihan");
	gtk_window_set_resizable(GTK_WINDOW(w->appletWindow), FALSE);
	gtk_window_set_decorated(GTK_WINDOW(w->appletWindow), FALSE);
	gtk_window_stick(GTK_WINDOW(w->appletWindow));
	gtk_window_set_gravity(GTK_WINDOW(w->appletWindow), GDK_GRAVITY_SOUTH_WEST);
	//gtk_window_set_accept_focus(GTK_WINDOW(w->appletWindow), FALSE);
	gtk_window_move(GTK_WINDOW(w->appletWindow), 0,
			(gdk_screen_height() > 64 ? gdk_screen_height() - 64 : 0));
	g_signal_connect(G_OBJECT(w->appletWindow), "delete-event",
			 G_CALLBACK(gtk_true), NULL);
        
	w->appletFrame = gtk_frame_new(NULL);
	gtk_frame_set_shadow_type(GTK_FRAME(w->appletFrame), GTK_SHADOW_OUT);
	gtk_container_add(GTK_CONTAINER(w->appletWindow), w->appletFrame);

        w->controls = status_controls_new(w);

        w->mainMenu = create_main_menu();

        if (m->tcscAvailable)
                w->tcscMenu = create_tcsc_menu();
        
        return w;
}


/* ------------------------------------------------------------------------- */
static void
status_window_free(StatusWindow *w)
{
        status_controls_free(w->controls);
        g_free(w);
        // FIXME: lots more to free here
}




/* ------------------------------------------------------------------------- */
static void
conversion_off_handler(GtkMenuItem *w, gpointer data)
{
	int int_values[1];

	if (im_menu_ignore_activate)
		return;

	if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)))
		return;

	int_values[0] = UNIHAN_ENGINE_CONV_OFF;
	send_to_engine(1, int_values, 0, NULL);
}

/* ------------------------------------------------------------------------- */
static void
switch_im_handler(GtkMenuItem *w, gpointer data)
{
	int int_values[1];
	char *string_values[1];
	IMInfo *inf = (IMInfo *)data;

	if (im_menu_ignore_activate)
		return;

	//if (!w->active) return;
	if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)))
		return;

#if DEBUG
	g_message("switching im to %s", inf->id);
#endif

	int_values[0] = UNIHAN_ENGINE_SWITCH_IM;
	string_values[0] = inf->id;
	send_to_engine(1, int_values, 1, string_values);
}

/* ------------------------------------------------------------------------- */
static void
tcsc_item_handler(GtkMenuItem *w, gpointer data)
{
	int tcsc = GPOINTER_TO_INT(data);
	int int_values[2] = { UNIHAN_ENGINE_SET_TCSC_MODE, tcsc };
#if DEBUG
	g_message("tcsc_item_handler, %d", tcsc);
#endif
	if (stop_sending_tcsc) {
		stop_sending_tcsc = FALSE;
#if DEBUG
g_message("stop_sending_tcsc to FALSE");
#endif
		return;
	}
	send_to_engine(2, int_values, 0, NULL);
}

/* ------------------------------------------------------------------------- */
static GtkWidget *
create_menu_separator(void)
{
	GtkWidget *item;

	item = gtk_menu_item_new();
	gtk_widget_set_sensitive(item, FALSE);
	gtk_widget_show(item);

	return item;
}


/* ------------------------------------------------------------------------- */
static void
create_im_menu(GtkWidget *w, gpointer data)
{
	GSList 		*menugrp = NULL;
	GtkWidget 	*eng_item;
	//const gchar 	*curr_im_name = gtk_label_get_text(GTK_LABEL(im_label));

	if (!window->imMenu) {
		window->imMenu = gtk_menu_new();

		g_signal_connect(G_OBJECT(window->imMenu), "enter-notify-event",
				 G_CALLBACK(applet_menu_enter_notify_event_handler), NULL);
		g_signal_connect(G_OBJECT(window->imMenu), "leave-notify-event",
				 G_CALLBACK(applet_menu_leave_notify_event_handler), NULL);

		gtk_menu_prepend(GTK_MENU(window->imMenu), create_menu_separator());

		eng_item = gtk_radio_menu_item_new_with_label(menugrp, _("English"));
		menugrp = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(eng_item));
		if (!window->model->currIM) {
			gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(eng_item), TRUE);
		}
		g_signal_connect(G_OBJECT(eng_item), "activate",
				 G_CALLBACK(conversion_off_handler), NULL);
		gtk_widget_show(eng_item);
		gtk_menu_prepend(GTK_MENU(window->imMenu), eng_item);

                GList *im_list_ = window->model->imCurrList;
		while (im_list_) {
			IMInfo *im = (IMInfo *)im_list_->data;
			GtkWidget *item;

			item = gtk_radio_menu_item_new_with_label(menugrp, im->cname);
			menugrp = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item));
			if (im == window->model->currIM) {
				gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
			}
			g_signal_connect(G_OBJECT(item), "activate",
					 G_CALLBACK(switch_im_handler), im);
	
			gtk_widget_show_all(item);
			gtk_menu_append(GTK_MENU(window->imMenu), item);
			im_list_ = im_list_->next;
		}

		gtk_menu_append(GTK_MENU(window->imMenu), create_menu_separator());

		gtk_widget_show_all(window->imMenu);
		g_object_ref(window->imMenu);

	} else {
		// update the IM menu
		GList *items = GTK_MENU_SHELL(window->imMenu)->children;
		
		im_menu_ignore_activate = TRUE;

		for (; items; items = g_list_next(items)) {
			GtkWidget *itemLabel = gtk_bin_get_child(GTK_BIN(items->data));
			const char *itemName = NULL;
			if (!itemLabel || !GTK_IS_LABEL(itemLabel))
				continue;
			itemName = gtk_label_get_text(GTK_LABEL(itemLabel));
			if (window->model->currIM && itemName && !strcmp(window->model->currIM->cname, itemName)) {
				gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(items->data), TRUE);
			}
			else if (!window->model->currIM) {
				gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(items->data),
						TRUE);
				break;
			}
		}

		im_menu_ignore_activate = FALSE;
	}
	gtk_menu_popup(GTK_MENU(window->imMenu), NULL, NULL, 
		       menu_position_func, w, 1, GDK_CURRENT_TIME);
}

/* ------------------------------------------------------------------------- */
static void
plug_embedded_handler(GtkPlug *w, gpointer data)
{
	// already plugged into something else?
	if (window->runAsApplet == TRUE)
		return;
	gtk_widget_hide_all(GTK_WIDGET(window->appletWindow));
	gtk_widget_ref(GTK_WIDGET(window->controls->hbox));
	gtk_container_remove(GTK_CONTAINER(window->appletFrame), GTK_WIDGET(window->controls->hbox));
	gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(window->controls->hbox));
	gtk_widget_unref(GTK_WIDGET(window->controls->hbox));
	gtk_widget_show_all(GTK_WIDGET(w));
	window->runAsApplet = TRUE;
}

/* ------------------------------------------------------------------------- */
static void
set_window_icon(void)
{
	GdkPixbuf *pixbuf = NULL;
	GList *iconlist = NULL;

	pixbuf = gdk_pixbuf_new_from_file(UNIHAN_DATA_DIR "/images/icon.png", NULL);
	if (!pixbuf)
		return;
	iconlist = g_list_append(iconlist, pixbuf);
	gtk_window_set_default_icon_list(iconlist);
	g_list_free(iconlist);
	g_object_unref(G_OBJECT(pixbuf));
}

/* ------------------------------------------------------------------------- */
static gboolean
is_unicode_charset(gchar *charset)
{
	return (charset && 
		(!strcmp("UTF-8", orig_codeset) ||
		 !strcmp("GB18030", orig_codeset)));
}


/* ------------------------------------------------------------------------- */
static void
applet_window_realize(GtkWidget *w, gpointer data)
{
	XWMHints *wmHints = NULL;
	Atom wmProtocols[2];
	Display *dpy = GDK_DISPLAY();

	// Do our best to convince the window manager _not_ 
	// to give us focus
	wmHints = XGetWMHints(dpy, GDK_WINDOW_XWINDOW(w->window));
        if (wmHints) {
                wmHints->flags |= InputHint;
                wmHints->input = False;
                XSetWMHints(dpy, GDK_WINDOW_XWINDOW(w->window), wmHints);
                XFree(wmHints);
        }

	// Get rid of the WM_TAKE_FOCUS atom that GDK puts onto
	// the window.  See the ICCCM section 4.1.7.
	wmProtocols[0] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
	wmProtocols[1] = XInternAtom(dpy, "_NET_WM_PING", False);
	XSetWMProtocols(dpy, GDK_WINDOW_XWINDOW(w->window), wmProtocols, 2);
	gdk_window_set_type_hint(w->window, GDK_WINDOW_TYPE_HINT_TOOLBAR);

	gtk_selection_owner_set(w, commit_string_atom, GDK_CURRENT_TIME);
#if DEBUG
	g_message("claimed selection owner");
#endif
}

/* ------------------------------------------------------------------------- */
static void
menu_position_func(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
{
	GtkWidget *activator = GTK_WIDGET(data);
	gint screenW = gdk_screen_width();
	gint screenH = gdk_screen_height();
	gint menuW, menuH;
	gint menuX, menuY;

	g_return_if_fail(menu != NULL);
	g_return_if_fail(x != NULL);
	g_return_if_fail(y != NULL);

	menuW = GTK_WIDGET(menu)->requisition.width;
	menuH = GTK_WIDGET(menu)->requisition.height;

	if (!gdk_window_get_origin(activator->window, &menuX, &menuY)) {
		return;
	}
	menuX += activator->allocation.x;
	menuY += activator->allocation.y;

	if ((menuY + activator->allocation.height + menuH) <= screenH)
		menuY += activator->allocation.height;
	else if ((menuY - menuH) >= 0)
		menuY -= menuH;
	else if (screenH - (menuY + activator->allocation.height) > menuY)
		menuY += activator->allocation.height;
	else
		menuY -= menuH;

	if ((menuX + menuW) > screenW) {
		menuX = screenW - menuW;
		if (menuX < 0)
			menuX = 0;
	}

	*x = menuX;
	*y = menuY;
}

/* ------------------------------------------------------------------------- */
static void
clipboard_get_handler(GtkClipboard *cb, GtkSelectionData *sel_data, guint info, gpointer data)
{
	gchar *target = gdk_atom_name(sel_data->target);

#if DEBUG
	g_message("someone getting clipboard! target = %s", target);
#endif

	switch (info) {
	case TARGET_CLIENT_WINDOW: {
		GdkNativeWindow window_id;
		guchar *window_id_mem;

		window_id = GDK_WINDOW_XWINDOW(GDK_WINDOW(window->appletWindow));
		window_id_mem = (guchar *)&window_id;
		gtk_selection_data_set(sel_data, GDK_SELECTION_TYPE_WINDOW,
				32, window_id_mem, 4);
		break;
	}

	case TARGET_PLUG_ID: {
		GdkNativeWindow plug_id;
		guchar *plug_id_mem;

		plug_id = gtk_plug_get_id(GTK_PLUG(plug));
		plug_id_mem = (guchar *)&plug_id;
		gtk_selection_data_set(sel_data, GDK_SELECTION_TYPE_WINDOW,
				32, plug_id_mem, 4);
		break;
	}

	case TARGET_ALL_IM_LIST:
	case TARGET_CURR_IM_LIST: {
		GList *im_list_ = (info == TARGET_ALL_IM_LIST) ?
				model->imList : model->imCurrList;
		guint num_im = g_list_length(im_list_);
		gchar **str_list = NULL;
		gchar *final_str = NULL;
		int i;

		str_list = g_new0(gchar*, num_im * 2 + 2);
		for (i = 0; im_list_; i++, im_list_ = g_list_next(im_list_)) {
			IMInfo *inf = (IMInfo *)im_list_->data;

			str_list[i*2 + 1] = inf->id;
			str_list[i*2 + 2] = inf->cname;
		}
		str_list[0] = g_strdup_printf("%u", num_im);
		final_str = g_strjoinv("\n", str_list);

		gtk_selection_data_set(sel_data, gdk_atom_intern("UTF8_STRING", FALSE),
				8, final_str, strlen(final_str));
		g_free(str_list[0]);
		g_free(str_list);
		g_free(final_str);
		break;
	}
	case TARGET_RAW_DATA:
		break;
	case TARGET_CLIPBOARD_COMMAND: {
		GtkSelectionData *selection_data;

		selection_data = gtk_clipboard_wait_for_contents(user_clipboard,
				gdk_atom_intern(RAW_DATA, FALSE));

		gtk_selection_data_free(selection_data);
		break;
	}
	} // switch
	g_free(target);
}
